/*
 *   Copyright (C) 2010  Jens Kieselbach
 * 
 *   This program is free software: you can redistribute it and/or modify
 *   it under the terms of the GNU General Public License as published by
 *   the Free Software Foundation, either version 3 of the License, or
 *   (at your option) any later version.
 * 
 *   This program is distributed in the hope that it will be useful,
 *   but WITHOUT ANY WARRANTY; without even the implied warranty of
 *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *   GNU General Public License for more details.
 * 
 *   You may obtain a copy of the License at http://www.gnu.org/licenses/gpl.html
 */

package de.kieselbach.grunner

import de.kieselbach.grunner.dialog.*
import de.kieselbach.grunner.io.*
import de.kieselbach.grunner.value.*
import de.kieselbach.swing.*
import de.kieselbach.swing.table.*

import groovy.xml.*
import java.util.logging.*
import java.text.*
import javax.swing.*

import java.awt.BorderLayout
import java.awt.Color

class MainFrame {

    static def dateFormat = new SimpleDateFormat("dd.MM.yyyy HH:mm:ss")
    static def dayFormat = new SimpleDateFormat("EE")
    static def monthFormat = new SimpleDateFormat("MMMM, yyyy")
	static def yearFormat = new SimpleDateFormat("yyyy")
	
    static def monthSummaryColor = new Color(152, 255, 143)
    static def weekSummaryColor = new Color(210, 255, 206)
	static def yearSummaryColor = new Color(255, 210, 74)
	static def totalSummaryColor = new Color(255, 210, 74)
	
    def uiResources
	def uiSet
    def resourceBundle

    def frame

    def path
    int timeCorrection
    boolean lnfFrame = true
    boolean lnfDialog = true

    def table
    def model
	def activities
	
    def progress
    def status

	def dayButton
	def weekButton
	def monthButton
	def yearButton
	def totalButton
	
	// -----------------------------------
	
    def doRefesh = {event ->
        readFiles()
    }

    def doSettings = {event ->
        settings()
    }

    def doExit = {event ->
        exit()
    }

    def doAbout = {event ->
        new AboutDialog(this).show()
    }

    MainFrame(def uires) {

        uiResources = uires
    }

    void init() {

        frame = new JFrame()
        frame.defaultCloseOperation = JFrame.DO_NOTHING_ON_CLOSE
        frame.windowClosing = doExit

        frame.iconImage = uiResources.images['grunner'].image
        resourceBundle = uiResources.resourceBundles['gui']

        frame.title = resourceBundle.getString('main.title')

        uiSet = uiResources.createUISet('main')
        uiSet.actions['refresh'] = doRefesh
        uiSet.actions['settings'] = doSettings
        uiSet.actions['exit'] = doExit
        uiSet.actions['about'] = doAbout
		
        frame.setJMenuBar(uiSet.menuBar)

        def main = new JPanel(new BorderLayout(0, 0))
        frame.contentPane = main
		
        main.add(uiSet.toolBar, BorderLayout.NORTH)

        // -----------------------------------

        table = new JTable()
        model = new CommonTableModel([  '',
                                        resourceBundle.getString('main.date'),
                                        resourceBundle.getString('main.duration'),
                                        resourceBundle.getString('main.distance'),
                                        resourceBundle.getString('main.laps'),
                                        resourceBundle.getString('main.averagepace'),
                                        resourceBundle.getString('main.maxpace'),
                                        resourceBundle.getString('main.averagebpm'),
                                        resourceBundle.getString('main.maxpbpm'),
                                        resourceBundle.getString('main.calories')] as ArrayList)
        model.valueClass = {col ->
            String.class
        }

        model.valueEditable = {rowval, row, col ->
            false
        }

        def formatduration = {val ->

            if(val == null) {
                return null
            }

            int d = val as int
            int h = val / 3600
            int m = (d % 3600) / 60
            int s = d % 60

            def buf = new StringBuffer(h as String)
            buf.append(':');
            if(m < 10) {
                buf.append('0');
            }
            buf.append(m);
            buf.append(':');
            if(s < 10) {
                buf.append('0');
            }
            buf.append(s);

            return buf.toString()
        }

        def formatpace = {val ->

            if(val == null) {
                return null
            }

            int m = (val / 60) as int
            int s = ((val - (m * 60)) + 0.5) as int

            def buf = new StringBuffer(m as String)
            buf.append(':');
            if(s < 10) {
                buf.append('0');
            }
            buf.append(s);

            return buf.toString()
        }

        model.valueGetter = {rowval, row, col ->

            def value
            def color = null
            int halign = SwingConstants.RIGHT

            switch(col) {
                case 0:
                    if(rowval.metaType > 0) {
                        value = rowval.description
                        halign = SwingConstants.LEFT
                    }
                    else {
                        value = rowval.date != null ? dayFormat.format(rowval.date) : null
                    }
                    break
                case 1:
                    value = rowval.date != null ? dateFormat.format(rowval.date) : null
                    break
                case 2:
                    value = formatduration(rowval.duration)
                    break
                case 3:
                    value = rowval.distance != null ? (rowval.distance / 1000).setScale(2, BigDecimal.ROUND_HALF_UP) : null
                    break
                case 4:
                    value = rowval.laps
                    break
                case 5:
                    value = formatpace(rowval.averagePace)
                    break
                case 6:
                    value = formatpace(rowval.maximumPace)
                    break
                case 7:
                    value = rowval.averageHeartRate > 0 ? rowval.averageHeartRate : null
                    break
                case 8:
                    value = rowval.maximumHeartRate > 0 ? rowval.maximumHeartRate : null
                    break
                case 9:
                    value = rowval.calories
                    break
            }

            if(rowval.metaType == Activity.MONTH_SUMMARY) {
                color = monthSummaryColor
            }
            else if(rowval.metaType == Activity.WEEK_SUMMARY) {
                color = weekSummaryColor
            }
			else if(rowval.metaType == Activity.YEAR_SUMMARY) {
				color = yearSummaryColor
			}
			else if(rowval.metaType == Activity.TOTAL_SUMMARY) {
				color = totalSummaryColor
			}

            return new TableCellValue(value, color, halign)
        }

        table.model = model

        def rightrenderer = new ColorTableCellRenderer()
        rightrenderer.horizontalAlignment = SwingConstants.RIGHT
        table.getColumnModel().getColumn(0).setCellRenderer(rightrenderer)
        table.getColumnModel().getColumn(1).setCellRenderer(rightrenderer)
        table.getColumnModel().getColumn(2).setCellRenderer(rightrenderer)
        table.getColumnModel().getColumn(3).setCellRenderer(rightrenderer)
        table.getColumnModel().getColumn(4).setCellRenderer(rightrenderer)
        table.getColumnModel().getColumn(5).setCellRenderer(rightrenderer)
        table.getColumnModel().getColumn(6).setCellRenderer(rightrenderer)
        table.getColumnModel().getColumn(7).setCellRenderer(rightrenderer)
        table.getColumnModel().getColumn(8).setCellRenderer(rightrenderer)
        table.getColumnModel().getColumn(9).setCellRenderer(rightrenderer)

        // ------------------------------------------------------------

		def activitypanel = new JPanel(new BorderLayout(0, 0))
		
        activitypanel.add(new JScrollPane(table), BorderLayout.CENTER)

        def statuspanel = new JPanel(new BorderLayout(5, 5))
        statuspanel.border = BorderFactory.createEmptyBorder(4, 0, 0 ,0)
        activitypanel.add(statuspanel, BorderLayout.SOUTH)

		def box = Box.createHorizontalBox()
		statuspanel.add(box, BorderLayout.WEST)
		
		dayButton = new JToggleButton(resourceBundle.getString('main.day'))
		weekButton = new JToggleButton(resourceBundle.getString('main.week'))
		monthButton = new JToggleButton(resourceBundle.getString('main.month'))
		yearButton = new JToggleButton(resourceBundle.getString('main.year'))
		totalButton = new JToggleButton(resourceBundle.getString('main.total'))
		
		box.add(dayButton)
		box.add(weekButton)
		box.add(monthButton)
		box.add(yearButton)
		box.add(totalButton)
		
		dayButton.actionPerformed = {applyFilter()}
		weekButton.actionPerformed = {applyFilter()}
		monthButton.actionPerformed = {applyFilter()}
		yearButton.actionPerformed = {applyFilter()}
		totalButton.actionPerformed = {applyFilter()}
		
        progress = new JProgressBar(SwingConstants.HORIZONTAL)
        statuspanel.add(progress, BorderLayout.EAST)

        status = new JLabel();
        status.horizontalAlignment = SwingConstants.RIGHT
        statuspanel.add(status, BorderLayout.CENTER)
		
		// -----------------------------------------------------------------

		def diagrampanel = new JPanel(new BorderLayout(0, 0))
		
		// -----------------------------------------------------------------
		
		def splitpane = new CleanSplitPane(JSplitPane.VERTICAL_SPLIT, true, activitypanel, diagrampanel)
		main.add(splitpane, BorderLayout.CENTER)
    }

    void settings() {

        def dlg = new SettingsDialog(this)
        dlg.path = path
        dlg.timeCorrection = timeCorrection
        dlg.lnfFrame = lnfFrame
        dlg.lnfDialog = lnfDialog
        dlg.show()
        if(dlg.wasOk) {
            boolean restart = lnfFrame != dlg.lnfFrame

            path = dlg.path
            timeCorrection = dlg.timeCorrection
            lnfFrame = dlg.lnfFrame
            lnfDialog = dlg.lnfDialog

            JFrame.setDefaultLookAndFeelDecorated(lnfFrame)
            JDialog.setDefaultLookAndFeelDecorated(lnfDialog)

            if(restart && new PaneBuilder().question(resourceBundle.getString('main.title'), message: resourceBundle.getString('main.restart'), owner: frame, options: ['yes', 'no'], default: 'no') == 'yes') {
                exit(false)
                show()
            }
        }
    }

    void readFiles() {

        File dir = new File(path != null ? path : '')
        if(!dir.directory) {
             new PaneBuilder().error(resourceBundle.getString('main.title'), message: resourceBundle.getString('main.configpatherror'), owner: frame)
        }

        progress.indeterminate = true
        status.text = resourceBundle.getString('main.scandirectory')
		disableFields()
		
        Thread.start() {
            def result;

            try {
                def files = [] as List

                dir.eachFileRecurse() {file ->
                    if(file.file && file.toString().toLowerCase().endsWith('.tcx')) {
                        files << file
                    }
                }

                SwingUtilities.invokeLater {
                    progress.indeterminate = false
                    progress.maximum = files.size()
                    progress.value = 0
                    status.text = resourceBundle.getString('main.readfiles')
                }

                int num = 0
                def actlist = [] as List

                for(File file : files) {
                    def list =  TcxReader.readFile(file, timeCorrection)
                    if(list != null) {
                        list.each() {act ->
                            actlist << act
                        }
                    }

                    num++
                    SwingUtilities.invokeLater {
                        progress.value = num
                    }
                }

                activities = adjustActvities(actlist)

                result = {
                    applyFilter()
                }
            }
            catch(Exception exc) {
                result = {
                    new PaneBuilder().error(resourceBundle.getString('main.title'), message: resourceBundle.getString('main.readerror'), owner: frame, exception: exc)
                }
            }
            
            SwingUtilities.invokeLater {

				enableFields()
                progress.indeterminate = false
                progress.value = 0
                status.text = ''
                if(result != null) {
                    result()
                }
            }
        }
    }

    def adjustActvities(def activities) {

        Collections.sort(activities, {o1, o2 ->
            if(o1.date == null && o2.date == null) {
                return 0
            }
            else if(o1.date == null) {
                return -1
            }
            else {
                return o1.date.compareTo(o2.date)
            }
        } as Comparator)

        
        def adjusted = [] as List

		int tcount = 0
		int tpcount = 0

		def total = new Activity()
		total.metaType = Activity.TOTAL_SUMMARY
		total.description = resourceBundle.getString('main.total')
		total.duration = 0
		total.distance = 0
		total.laps = 0
		total.averagePace = 0
		total.maximumPace = 0
		total.averageHeartRate = 0
		total.maximumHeartRate = 0
		total.calories = 0
		
        if(activities.size() > 0) {
            def calendar = Calendar.getInstance()
            calendar.time = activities[0].date

			int ycount = 0
			int ypcount = 0

			int yearid = calendar.get(Calendar.YEAR)
			def year = new Activity()
			adjusted << year
			year.metaType = Activity.YEAR_SUMMARY
			year.description = yearFormat.format(calendar.time)
			year.duration = 0
			year.distance = 0
			year.laps = 0
			year.averagePace = 0
			year.maximumPace = 0
			year.averageHeartRate = 0
			year.maximumHeartRate = 0
			year.calories = 0
			
            int mcount = 0
            int mpcount = 0

            int monthid = calendar.get(Calendar.MONTH)
            def month = new Activity()
            adjusted << month
            month.metaType = Activity.MONTH_SUMMARY
            month.description = monthFormat.format(calendar.time)
            month.duration = 0
            month.distance = 0
            month.laps = 0
            month.averagePace = 0
            month.maximumPace = 0
            month.averageHeartRate = 0
            month.maximumHeartRate = 0
            month.calories = 0

            int wcount = 0
            int wpcount = 0

            int weekid = calendar.get(Calendar.WEEK_OF_YEAR)
            def week = new Activity()
            adjusted << week
            week.metaType = Activity.WEEK_SUMMARY
            week.description = resourceBundle.getString('main.weekofyear') + ' ' + weekid
            week.duration = 0
            week.distance = 0
            week.laps = 0
            week.averagePace = 0
            week.maximumPace = 0
            week.averageHeartRate = 0
            week.maximumHeartRate = 0
            week.calories = 0

            activities.each() {act ->

                calendar.time = act.date
				
				def yid = calendar.get(Calendar.YEAR)
				if(yid != yearid) {

					if(ycount > 0) {
						year.averagePace = year.averagePace / ycount
						if(ypcount > 0) {
							year.averageHeartRate = year.averageHeartRate / ypcount
						}
					}

					ycount = 0
					ypcount = 0
					yearid = yid
					year = new Activity()
					adjusted << year
					year.metaType = Activity.YEAR_SUMMARY
					year.description = yearFormat.format(calendar.time)
					year.duration = 0
					year.distance = 0
					year.laps = 0
					year.averagePace = 0
					year.maximumPace = 0
					year.averageHeartRate = 0
					year.maximumHeartRate = 0
					year.calories = 0
				}
				
                def mid = calendar.get(Calendar.MONTH)
                if(mid != monthid) {

                    if(mcount > 0) {
                        month.averagePace = month.averagePace / mcount
                        if(mpcount > 0) {
                            month.averageHeartRate = month.averageHeartRate / mpcount
                        }
                    }

                    mcount = 0
                    mpcount = 0
                    monthid = mid
                    month = new Activity()
                    adjusted << month
                    month.metaType = Activity.MONTH_SUMMARY
                    month.description = monthFormat.format(calendar.time)
                    month.duration = 0
                    month.distance = 0
                    month.laps = 0
                    month.averagePace = 0
                    month.maximumPace = 0
                    month.averageHeartRate = 0
                    month.maximumHeartRate = 0
                    month.calories = 0
                }

                def wid = calendar.get(Calendar.WEEK_OF_YEAR)
                if(wid != weekid) {

                    if(wcount > 0) {
                        week.averagePace = week.averagePace / wcount
                        if(wpcount > 0) {
                            week.averageHeartRate = week.averageHeartRate / wpcount
                        }
                    }

                    wcount = 0
                    wpcount = 0
                    weekid = wid
                    week = new Activity()
                    adjusted << week
                    week.metaType = Activity.WEEK_SUMMARY
                    week.description = resourceBundle.getString('main.weekofyear') + ' ' + weekid
                    week.duration = 0
                    week.distance = 0
                    week.laps = 0
                    week.averagePace = 0
                    week.maximumPace = 0
                    week.averageHeartRate = 0
                    week.maximumHeartRate = 0
                    week.calories = 0
                }

                // ----------------------------------------------

				total.duration += act.duration
				total.distance += act.distance
				total.laps += act.laps
				total.averagePace += act.averagePace
				if(total.maximumPace == 0 || act.maximumPace < total.maximumPace) {
					total.maximumPace = act.maximumPace
				}
				total.averageHeartRate += act.averageHeartRate
				if(act.maximumHeartRate  > total.maximumHeartRate) {
					total.maximumHeartRate = act.maximumHeartRate
				}
				total.calories += act.calories

                year.duration += act.duration
                year.distance += act.distance
                year.laps += act.laps
                year.averagePace += act.averagePace
                if(year.maximumPace == 0 || act.maximumPace < year.maximumPace) {
                    year.maximumPace = act.maximumPace
                }
                year.averageHeartRate += act.averageHeartRate
                if(act.maximumHeartRate  > year.maximumHeartRate) {
                    year.maximumHeartRate = act.maximumHeartRate
                }
                year.calories += act.calories
				
                month.duration += act.duration
                month.distance += act.distance
                month.laps += act.laps
                month.averagePace += act.averagePace
                if(month.maximumPace == 0 || act.maximumPace < month.maximumPace) {
                    month.maximumPace = act.maximumPace
                }
                month.averageHeartRate += act.averageHeartRate
                if(act.maximumHeartRate  > month.maximumHeartRate) {
                    month.maximumHeartRate = act.maximumHeartRate
                }
                month.calories += act.calories

                week.duration += act.duration
                week.distance += act.distance
                week.laps += act.laps
                week.averagePace += act.averagePace
                if(week.maximumPace == 0 || act.maximumPace < week.maximumPace) {
                    week.maximumPace = act.maximumPace
                }
                week.averageHeartRate += act.averageHeartRate
                if(act.maximumHeartRate  > week.maximumHeartRate) {
                    week.maximumHeartRate = act.maximumHeartRate
                }
                week.calories += act.calories

                if(act.averageHeartRate > 0) {
                    tpcount++
                    ypcount++
                    mpcount++
                    wpcount++
                }
                tcount++
                ycount++
                mcount++
                wcount++

                adjusted << act
            }

            if(ycount > 0) {
                year.averagePace = year.averagePace / ycount
                if(ypcount > 0) {
                    year.averageHeartRate = year.averageHeartRate / ypcount
                }
            }
			
			if(mcount > 0) {
				month.averagePace = month.averagePace / mcount
				if(mpcount > 0) {
					month.averageHeartRate = month.averageHeartRate / mpcount
				}
			}

            if(wcount > 0) {
                week.averagePace = week.averagePace / wcount
                if(wpcount > 0) {
                    week.averageHeartRate = week.averageHeartRate / wpcount
                }
            }
        }
		
		if(tcount > 0) {
			total.averagePace = total.averagePace / tcount
			if(tpcount > 0) {
				total.averageHeartRate = total.averageHeartRate / tpcount
			}
		}
		
		adjusted << total
        
        return adjusted
    }
	
	void applyFilter() {
		
		boolean day = dayButton.selected
		boolean week = weekButton.selected
		boolean month = monthButton.selected
		boolean year = yearButton.selected
		boolean total = totalButton.selected
		
		def list = [] as List
		
		activities.each() {act ->
			if((act.metaType == Activity.MONTH_SUMMARY && month) || (act.metaType == Activity.WEEK_SUMMARY && week) ||
					(act.metaType == Activity.YEAR_SUMMARY && year) || (act.metaType == Activity.TOTAL_SUMMARY && total) ||
					(act.metaType == 0 && day)) {
				list << act
			}
		}
		
		model.setRows(list)
		TableUtilities.setOptimalColumnWidths(table)
	}

	void enableFields() {
		
		uiSet.enable('refresh')
		uiSet.enable('settings')
		uiSet.enable('exit')
		uiSet.enable('about')
		
		dayButton.enabled = true
		weekButton.enabled = true
		monthButton.enabled = true
		yearButton.enabled = true
		totalButton.enabled = true
	}
	
	void disableFields() {
		
		uiSet.disable('refresh')
		uiSet.disable('settings')
		uiSet.disable('exit')
		uiSet.disable('about')
		
		dayButton.enabled = false
		weekButton.enabled = false
		monthButton.enabled = false
		yearButton.enabled = false
		totalButton.enabled = false
	}

    // -----------------------------------------------------------------------------------------------------------------------

    final def SETTINGS_FILE = '.groovyrunner'

    void show() {

        def x = 0
        def y = 0
        def w = 600
        def h = 400
		def day = true
		def week = true
		def month = true
		def year = true
		def total = true
		
        def toBoolean = {str, defval ->
            if ('true'.equalsIgnoreCase(str)) {
                return true
            }
            else if ('false'.equalsIgnoreCase(str)) {
                return false
            }
            else {
                return defval
            }
        }

        def toInt = {str, defval ->
            if (str != null && str.isInteger()) {
                return Integer.parseInt(str)
            }
            else {
                return defval
            }
        }

        try {
            def file = new File((System.properties.'user.home' + System.properties.'file.separator' + SETTINGS_FILE) as String)
            if(file.exists()) {
                file.withInputStream {input ->
                    def result = new XmlParser().parse(input)

                    if (result.'mainframe'.size() == 1) {
                        x = toInt(result.'mainframe'[0].'@x', x)
                        y = toInt(result.'mainframe'[0].'@y', y)
                        w = toInt(result.'mainframe'[0].'@width', w)
                        h = toInt(result.'mainframe'[0].'@height', h)
						
						day = toBoolean(result.'mainframe'[0].'@day', day)
						week = toBoolean(result.'mainframe'[0].'@week', week)
						month = toBoolean(result.'mainframe'[0].'@month', month)
						year = toBoolean(result.'mainframe'[0].'@year', year)
						total = toBoolean(result.'mainframe'[0].'@total', total)
                    }
                    else if (result.'mainframe'.size() > 1) {
                        throw new IOException('the settings file is invalid!')
                    }

                    if (result.'settings'.size() == 1) {
                        path = result.'settings'[0].'@path'
                        if(result.'settings'[0].'@timecorrection' != null) {
                            timeCorrection = result.'settings'[0].'@timecorrection' as int
                        }
                        lnfFrame = !'false'.equalsIgnoreCase(result.'settings'[0].'@lnfframe')
                        lnfDialog = !'false'.equalsIgnoreCase(result.'settings'[0].'@lnfdialog')
                    }
                    else if (result.'settings'.size() > 1) {
                        throw new IOException('the settings file is invalid!')
                    }
                }
            }
        }
        catch (Exception exc) {
            Logger.getLogger(getClass().name).log(Level.SEVERE, 'Unable to load settings!', exc)
        }

        JFrame.setDefaultLookAndFeelDecorated(lnfFrame)
        JDialog.setDefaultLookAndFeelDecorated(lnfDialog)

        init()
		
		dayButton.selected = day
		weekButton.selected = week
		monthButton.selected = month
		yearButton.selected = year
		totalButton.selected = total
		
        frame.size = [w, h]
        frame.location = [x, y]
        frame.visible = true

        if(path != null && path.length() > 0) {
            readFiles()
        }
    }

    void exit(boolean exit = true) {

        frame.visible = false
        frame.dispose()

        try {
            def file = new File((System.properties.'user.home' + System.properties.'file.separator' + SETTINGS_FILE) as String)

            file.withPrintWriter 'UTF-8', {writer ->

                writer.println('<?xml version="1.0" encoding="UTF-8"?>')
                writer.println()

                def xml = new MarkupBuilder(writer as PrintWriter)
                xml.groovyrunner() {

                    mainframe(x: frame.location.@x as String, y: frame.location.@y as String, width: frame.width as String, height: frame.height as String,
							day: dayButton.selected as String, week: weekButton.selected as String, month: monthButton.selected as String, 
							year: yearButton.selected as String, total: totalButton.selected as String) {
                    }

                    settings(path: path, timecorrection: timeCorrection as String, lnfframe: lnfFrame as String, lnfdialog: lnfDialog as String) {
                    }
                }
            }
        }
        catch (Exception exc) {
            Logger.getLogger(getClass().name).log(Level.SEVERE, 'Unable to save settings!', exc)
        }

        if(exit) {
            System.exit(0)
        }
    }
}
